## UNIX commander - простой визуальный Шелл. Головной модуль проекта uxcom.

Пример 23 - simple visual shell.
```cpp
#       UNIX commander
#########################################################################
# Это файл Makefile для проекта uxcom - простого меню-ориентированного
# экранного интерфейса для переходов по файловой системе.
# Ключ -Iкаталог указывает из какого каталога должны браться
# include-файлы, подключаемые по #include "имяФайла".
# Проект состоит из нескольких файлов:
# Пример 17, Пример 18, Пример 19, Пример 21, Пример 23 и других.
#
#  +  Left    Right   _Commands    Tools    Sorttype      +
#  |           /usr/a+---------------------008/013-+      |
#  +-----------------|        Главное меню         |---+--+
#  |      ..         +--------------------------+--+   |  |
#  |      .BAD       |  Current directory       |  |   |  |
#  |      .contents.m|  Root directory          |  |   |##|
#  |      DUMP       |  Menus                   |  |   |  |
#  |      Makefile   +--------------------------+  |   |  |
#  |      PLAN       |  Help                    |  |   |  |
#  |     _points     |  Unimplemented           |  |   |  |
#  |      table      |  Change sorttype         |##|   |  |
#  |     #unbold     | _Look directory history  |  |   |  |
#  |     #uxcom      +--------------------------+  |   |  |
#  |      x.++       |  Quit                    |  |   |  |
#  |      00         +--------------------------+  |   |  |
#  |      11         |  Redraw screen           |  |   |  |
#  |      LOOP_p     +--------------------------+--+   |  |
#  |      LOOP_q      .c     |       etc               |  |
#  |      LOOP_strt   .c     |       install           |  |
#  +-------------------------+-------------------------+  |
#  | points      165 -r--r-- | .cshrc  2509 -rw-r--r-- |  |
#  +-------------------------+-------------------------+  |
#  |  История путешествий                              |  |
#  +---------------------------------------------------+--+
#
SHELL=/bin/sh
SRCS = glob.c w.c menu.c pull.c match.c pwd.c hist.c line.c table.c \
       main.c treemk.c
OBJS = glob.o w.o menu.o pull.o match.o pwd.o hist.o line.o table.o \
       main.o treemk.o
# INCLUDE = /usr/include
# LIB     = -lncurses
INCLUDE   = -I../../src/curses
LIB       = ../../src/curses/libncurses.a
DEFINES   = -DUSG -DTERMIOS
CC        = cc  -O            # стандартный C-compiler + оптимизация
#CC       = gcc -O            # GNU C-compiler

uxcom: $(OBJS)
	$(CC) $(OBJS) -o $@ $(LIB)
	sync; ls -l $@; size $@
glob.o: glob.c glob.h   # это файл "Пример 18"
	$(CC) -c glob.c
w.o: w.c w.h            # это файл "Пример 17"
	$(CC) -c $(INCLUDE) $(DEFINES) w.c
menu.o: menu.c glob.h w.h menu.h   # это файл "Пример 19"
	$(CC) -c $(INCLUDE) $(DEFINES) menu.c
pull.o: pull.c glob.h w.h menu.h pull.h # это файл "Пример 20"
	$(CC) -c $(INCLUDE) $(DEFINES) pull.c
match.o: match.c
	$(CC) -c -DMATCHONLY \
	      -DMATCH_ERR="TblMatchErr()" match.c
pwd.o: pwd.c
	$(CC) -c -DU42 -DCWDONLY pwd.c
treemk.o: treemk.c
	$(CC) -c $(DEFINES) \
	      -DERR_CANT_READ=tree_err_cant_read     \
	      -DERR_NAME_TOO_LONG=tree_name_too_long \
	      -DTREEONLY -DU42 treemk.c
hist.o: hist.c hist.h glob.h menu.h w.h  # это файл "Пример 21"
	$(CC) -c $(INCLUDE) $(DEFINES) hist.c
line.o: line.c w.h glob.h menu.h hist.h line.h  # "Пример 21"
	$(CC) -c $(INCLUDE) $(DEFINES) line.c
table.o: table.c w.h glob.h menu.h table.h      # "Пример 22"
	$(CC) -c $(INCLUDE) $(DEFINES) table.c
main.o: main.c glob.h w.h menu.h hist.h line.h pull.h table.h
	$(CC) -c $(INCLUDE) $(DEFINES) main.c
w.h:    wcur.h
	touch w.h

/* _______________________ файл main.c __________________________ */
/* Ниже предполагается, что вы раскрасили в /etc/termcap          *
 * выделения A_STANDOUT и A_REVERSE в РАЗНЫЕ цвета !              */
#include "w.h"
#include "glob.h"
#include "menu.h"
#include "hist.h"
#include "line.h"
#include "table.h"
#include "pull.h"
#include <signal.h>
#include <ustat.h>
#include <locale.h>

void t_enter(), t_leave();
LineEdit    edit;                     /* редактор строки           */
Hist        hcwd, hedit, hpat;        /* истории:                  */
/* посещенные каталоги, набранные команды, шаблоны имен            */
Menu        mwrk, msort;              /* должны иметь класс static */
PullMenu    pull;

typedef enum { SEL_WRK=0, SEL_PANE1, SEL_PANE2, SEL_PULL, SEL_HELP } Sel;
Sel current_menu;       /* текущее активное меню                   */
Sel previous_menu;      /* предыдущее активное меню                */
#define SEL_PANE (current_menu == SEL_PANE1 || current_menu == SEL_PANE2)
typedef struct {
	Table t;        /* таблица с именами файлов                */
	DirContents d;  /* содержимое каталогов                    */
} FileWidget;
FileWidget tpane1, tpane2;    /* левая и правая панели             */
FileWidget *A_pane = &tpane1; /* активная панель                   */
FileWidget *B_pane = &tpane2; /* противоположная панель            */
#define A_tbl   (&A_pane->t)
#define A_dir   (&A_pane->d)
#define B_tbl   (&B_pane->t)
#define B_dir   (&B_pane->d)
#define TblFW(tbl) ((tbl) == A_tbl ? A_pane : B_pane)
void ExchangePanes(){  /* Обменять указатели на панели */
     FileWidget *tmp = A_pane; A_pane = B_pane; B_pane = tmp;
     current_menu = (current_menu == SEL_PANE1 ? SEL_PANE2 : SEL_PANE1);
}
#define Other_pane(p)  ((p) == A_pane ? B_pane : A_pane)
#define Other_tbl(t)   ((t) == A_tbl  ? B_tbl  : A_tbl )
WINDOW *panewin;        /* окно, содержащее обе панели = stdscr */
typedef enum { NORUN=0, RUNCMD=1, CHDIR=2, TAG=3, FIND=4 } RunType;

#define REPEAT_KEY 666  /* псевдоклавиша "повтори выбор в меню"    */
#define LEAVE_KEY  777  /* псевдоклавиша "покинь это меню"         */
#define NOSELECTED (-1) /* в меню ничего пока не выбрано           */
#define CENTER  (COLS/2-2) /* линия раздела панелей                */
int done;               /* закончена ли программа ?                */
char CWD[MAXLEN];       /* полное имя текущего каталога            */
char SELECTION[MAXLEN]; /* имя выбранного файла                    */
/*-----------------------------------------------------------------*/
/* Выдать подсказку в строке редактора                             */
/*-----------------------------------------------------------------*/
#include <stdarg.h>
void Message(char *s, ... ){
  char msg[80]; va_list args; int field_width;
  va_start(args, s); vsprintf(msg, s, args); va_end(args);
  wattrset    (panewin,     A_tbl->sel_attrib);
  field_width = A_tbl->width + B_tbl->width - 3;
  mvwprintw   (panewin, LINES-2, tpane1.t.left+1, " %*.*s ",
	       -field_width, field_width, msg);
  wattrset    (panewin, A_tbl->bg_attrib);
  wnoutrefresh(panewin);
}
/*-----------------------------------------------------------------*
 *      Меню порядка сортировки имен файлов.                       *
 *-----------------------------------------------------------------*/
Info sort_info[] = {
    { "По возрастанию", 0}, { "По убыванию",    0},
    { "По суффиксу",    0}, { "Без сортировки", 0},
    { "По размеру",     M_HATCH},
    { NULL, 0}
};
/* При входе в меню сортировки указать текущий тип сортировки */
void sort_show(Menu *m){
    MnuPointAt(&msort, (int) sorttype);
}
/* Выбрать тип сортировки имен файлов */
static void SelectSortType(int sel){
    if( sel == NOSELECTED )
	sel = MnuUsualSelect(&msort, NO);
    MnuHide(&msort);
    current_menu = previous_menu;
    if(M_REFUSED(&msort)) return;
    sorttype = (Sort) sel;
    A_dir->lastRead = B_dir->lastRead = 0L; /* форсировать перечитку */
    /* но ничего явно не пересортировывать и не перерисовывать       */
}
/*-----------------------------------------------------------------*
 *  Отслеживание содержимого каталогов и переинициализация меню.   *
 *-----------------------------------------------------------------*/
#define NON_VALID(d)  ((d)->readErrors || (d)->valid == NO)
/* Сменить содержимое таблицы и списка файлов */
void InitTblFromDir(FileWidget *wd, int chdired, char *savename){
     char *msg, *name; Table *tbl = &(wd->t); DirContents *d = &wd->d;
     int saveind  = tbl->current, saveshift = tbl->shift;
     char *svname = NULL;
     if(tbl->nitems > 0 ) svname = strdup(T_ITEMF(tbl, saveind, 0));
  /* Несуществующие и нечитаемые каталоги выделить особо */
     if( NON_VALID(d)) wattrset(tbl->win, A_REVERSE);
     TblClear(tbl);
     if(d->valid == NO){
	msg = "Не существует"; name = d->name; goto Report;
     } else if(d->readErrors){ /* тогда d->files->s == NULL */
	msg = "Не читается";   name = d->name;
Report: mvwaddstr(tbl->win, tbl->top + tbl->height/2,
		  tbl->left + (tbl->width - strlen(name))/2, name);
	mvwaddstr(tbl->win, tbl->top + tbl->height/2+1,
		  tbl->left + (tbl->width - strlen(msg))/2, msg);
     }
     wattrset(tbl->win, tbl->bg_attrib);
     tbl->items = d->files; TblInit(tbl, NO);
     /* Постараться сохранить позицию в таблице */
     if( chdired ) TblPlaceByName(tbl, savename);
     else {
	 if( svname == NULL || TblPlaceByName(tbl, svname) < 0 ){
	     tbl->shift   = saveshift;
	     tbl->current = saveind; TblChk(tbl);
	 }
     }
     if(svname) free(svname);
}
/* Перейти в каталог и запомнить его полное имя  */
int mychdir(char *newdir){ int code = chdir(newdir);
    if( code < 0 ) return code;
    getwd(CWD); in_the_root = (strcmp(CWD, "/") == 0);
    HistAdd(&hcwd, CWD, 0); /* запомнить в истории каталогов */
    t_enter(&tpane1.t);     /* на рамке нарисовать имя текущего каталога */
    return code;
}
/* Изменить текущий каталог и перечитать его содержимое */
int cd(char *newdir, FileWidget *wd, char *oldname){
    char oldbase[MAXLEN], *s, *strrchr(char *,char);
 /* Спасти в oldbase базовое имя старого каталога oldname (обычно CWD) */
    if(s = strrchr(oldname, '/')) s++; else s = oldname;
    strcpy(oldbase, s);

    if( mychdir(newdir) < 0){ /* не могу перейти в каталог */
	Message("Не могу перейти в %s", *newdir ? newdir : "???");
	beep(); return (-1); }
    if( ReadDir(CWD, &wd->d)){ /* содержимое изменилось */
	InitTblFromDir (wd, YES, oldbase);
	return 1;
    }
    return 0;
}
/* Проверить содержимое обеих панелей */
void checkBothPanes(){
   /* Случай NON_VALID нужен только для того, чтобы Init...
      восстановил "аварийную" картинку в панели */
      if( ReadDir(tpane1.d.name, &tpane1.d) || NON_VALID(&tpane1.d))
	  InitTblFromDir(&tpane1, NO, NULL);
      if( tpane1.t.exposed == NO ) TblDraw(&tpane1.t);
      if( ReadDir(tpane2.d.name, &tpane2.d) || NON_VALID(&tpane2.d))
	  InitTblFromDir(&tpane2, NO, NULL);
      if( tpane2.t.exposed == NO ) TblDraw(&tpane2.t);
}
/*-----------------------------------------------------------------*
 *    Ввод команд и выдача подсказки.                              *
 *-----------------------------------------------------------------*/
/* Особая обработка отдельных клавиш в редакторе строки */
char  e_move = NO; /* кнопки со стрелками <- -> двигают
      курсор по строке/по таблице */
int e_hit[] = { KEY_LEFT, KEY_RIGHT, KEY_UP, KEY_DOWN,
    KEY_F(0), KEY_IC,
    ctrl('G'), ctrl('E'), ctrl('L'), ctrl('F'), ctrl('X'), ctrl('Y'),
    -1 };
int e_handler (LineEdit *le, int c, HandlerReply *reply){
    *reply = HANDLER_CONTINUE;
    switch(c){
/* Перемещение по таблице без выхода из редактора строки */
    case KEY_LEFT:
	 if( !SEL_PANE || !e_move){
	      *reply=HANDLER_SWITCH; return c; }
	 TblPointAt(A_tbl, A_tbl->current - A_tbl->height); break;
    case KEY_RIGHT:
	 if( !SEL_PANE || !e_move){
	      *reply=HANDLER_SWITCH; return c; }
	 TblPointAt(A_tbl, A_tbl->current + A_tbl->height); break;
    case KEY_DOWN:
	 if( !SEL_PANE){ *reply=HANDLER_SWITCH; return c; }
	 TblPointAt(A_tbl, A_tbl->current + 1); break;
    case KEY_UP:
	 if( !SEL_PANE){ *reply=HANDLER_SWITCH; return c; }
	 TblPointAt(A_tbl, A_tbl->current - 1); break;
    case KEY_F(0):      /* F10 */
	 e_move = !e_move; break;
    case KEY_IC:
	 if( !SEL_PANE){ *reply=HANDLER_SWITCH; return c; }
	 TblRetag(A_tbl, A_tbl->current, T_LABEL);
	 TblPointAt(A_tbl, A_tbl->current+1);
	 break;
/* Подстановки */
    case ctrl('G'): /* подставить полное имя домашнего каталога */
	 LeInsStr(le, getenv("HOME")); LeInsStr(le, " "); break;
    case ctrl('E'): /* подставить имя выбранного файла */
	 if( A_tbl->nitems )
	     LeInsStr(le, T_ITEMF(A_tbl, A_tbl->current, 0));
	 LeInsStr(le, " "); break;
    case ctrl('L'): /* подставить имя выбранного файла из другой панели */
	 LeInsStr(le, T_ITEMF(B_tbl, B_tbl->current, 0));
	 LeInsStr(le, " "); break;
    case ctrl('X'): case ctrl('Y'):
    /* подстановка имен помеченных файлов */
    {    int label = (c == ctrl('X') ? T_LABEL : T_HATCH);
	 register i;
	 for(i=0; i < A_tbl->nitems && le->len < le->maxlen; ++i )
	     if( T_TST(A_tbl, i, label)){
		 LeInsStr(le, " "); LeInsStr(le, T_ITEMF(A_tbl, i, 0));
	     }
    } break;
    case ctrl('F'): /* подставить имя текущего каталога */
	 LeInsStr(le, CWD); LeInsStr(le, " "); break;
   }
   return c;
}
/* При начале редактирования ставь курсор в конец строки */
void e_pos (LineEdit *le){ le->pos = le->len; }
/* Обозначить, что мы покинули редактор строки */
void e_hide(LineEdit *le){
     le->sel_attrib = le->fr_attrib = le->bg_attrib = A_ITALICS;
     LeDraw(le);
}
/* Отредактировать строку в предпоследней строке окна */
char *Edit(WINDOW *w, char *src, RunType dorun){
    static char CMD[MAXLEN];   /* буфер для строки команды */
    int c;
    if(w != TOPW){ beep(); return NULL; }/* это должно быть верхнее окно */
    keypad(w, TRUE);
 /* Проинициализировать редактор строки */
    switch(dorun){
    case NORUN:  edit.histIn = edit.histOut = NULL;   break;
    case RUNCMD: edit.histIn = edit.histOut = &hedit; break;
    case FIND:
    case TAG:    edit.histIn = edit.histOut = &hpat;  break;
    case CHDIR:  edit.histIn = &hcwd; edit.histOut = NULL; break;
    }
    edit.line   = CMD;
    edit.maxlen = sizeof(CMD)-1;
    edit.top    = wlines(w)-2; edit.left = 2;
    edit.width  = wcols (w)-4 - (1+BARWIDTH);
    edit.insert = YES; edit.nc = YES;
    edit.win    = w;
    edit.wl_attrib  = edit.bg_attrib=A_REVERSE;
    edit.fr_attrib=A_STANDOUT; edit.sel_attrib = A_NORMAL|A_BLINK;
    edit.posMe   = e_pos;
    edit.hitkeys = (SEL_PANE ? e_hit : e_hit+5);
    edit.handler = e_handler;
    /* edit.hideMe  = e_hide; вызывается ЯВНО */
    /* остальные поля равны 0, т.к. edit - статическое данное */
    for(;;){
	strcpy(CMD, src); if(*src){ strcat(CMD, " "); }
	c = LeEdit( &edit );
	if( LE_REFUSED(&edit) || dorun != RUNCMD ||
	    !*CMD || c != '\n' ) break;
	/* курсор в нижнюю строку экрана */
	attrset(A_NORMAL); move(LINES-1, 0); refresh();
	resetterm();    /* приостановить работу curses-а    */
	putchar('\n');  /* промотать экран на строку        */
	system(CMD);    /* выполнить команду внешним Шеллом */
	fprintf(stderr,"Нажми ENTER чтобы продолжить --- ");gets(CMD);
	fixterm();      /* возобновить работу curses-а      */
	RedrawScreen(); /* перерисовать экран               */
	if(w == panewin){
	   checkBothPanes();
	   if(A_tbl->nitems) TblPoint(A_tbl, A_tbl->current, NO);
	}
	src = ""; /* во второй раз ничего не подставлять */
    }
    wattrset(w, A_NORMAL); /* ? */
    e_hide ( &edit );
    return ( *CMD && !LE_REFUSED(&edit)) ? CMD : NULL;
}
/* Выдача подсказки а также сообщений об ошибках.         */
/* В этом же окне можно набирать команды (dorun==RUNCMD). */
char *help(char *msg, RunType dorun){ register i; char *s;
    static char *helptext[] = {
	"ESC    - выход в главное меню",
	"F1     - подсказка",
	"INS    - пометить файл",
	"ctrl/E - подставить имя выбранного файла",
	"ctrl/L - подставить имя из другой панели",
	"ctrl/X - подставить помеченные файлы",
	"ctrl/Y - подставить помеченные курсивом",
	"ctrl/G - подставить имя домашнего каталога",
	"ctrl/F - подставить имя текущего каталога",
	"F4     - история",
	"F7     - переключить режим вставки/замены",
	"F10    - переключить перемещения по строке/по панели",
    };
#define HELPLINES (sizeof(helptext)/sizeof helptext[0])
    Sel save_current_menu = current_menu;
    /* "выскакивающее" POP-UP window */
    WINDOW *w = newwin(2+1+HELPLINES+1, 70, 2, (COLS-70)/2);
    if( w == NULL ) return NULL;
    current_menu = SEL_HELP;
    wattrset(w, A_REVERSE);    /* это будет инверсное окно  */
    werase  (w);               /* заполнить инверсным фоном */
    wborder(w);  RaiseWin(w);  /* окно появляется */
    if(*msg){                            wattron (w, A_BOLD);
      mvwaddstr(w, 1+HELPLINES, 2, msg); wattroff(w, A_BOLD);
    }
    for(i=0; i < HELPLINES; i++) mvwaddstr(w, 1+i, 2, helptext[i]);
    s = Edit(w, "", dorun); PopWin(); /* окно исчезает */
    current_menu = save_current_menu;
    return s;
}
/*-----------------------------------------------------------------*
 *   Управляющее меню.                                             *
 *-----------------------------------------------------------------*/
int f_left(), f_right(), f_pull(), f_help(), f_sort(), f_dir(),
    f_bye(),  f_redraw(),f_cdroot();
/* Обратите внимание, что можно указывать не все поля структуры,
 * а только первые. Остальные равны 0 */
#ifndef __GNUC__
Info mwrk_info[] = {    /* строки для главного меню      */
    { "\\Current directory",       0       , f_left  }, /* 0 */
    { "\\Root directory",          M_HATCH , f_right }, /* 1 */
    { "\\Menus",                   0       , f_pull  }, /* 2 */
    { "\1", /* гориз. черта */     0                 }, /* 3 */
    { "\\Help",                    0       , f_help  }, /* 4 */
    { "Un\\implemented",           I_NOSEL           }, /* 5 */
    { "Change \\sorttype",         0       , f_sort  }, /* 6 */
    { "Look directory \\history",  0       , f_dir   }, /* 7 */
    { "\1", /* гориз. черта */     0                 }, /* 8 */
    { "\\Quit",                    M_BOLD  , f_bye   }, /* 9 */
    { "\1", /* гориз. черта */     0                 }, /* 10 */
    { "\\Redraw screen",           M_HATCH , f_redraw}, /* 11 */
    { "Chdir both panels to /",    M_HATCH , f_cdroot}, /* 12 */
    { NULL, 0 }
};
#else /* GNU C-компилятор 1.37 не может инициализировать поля-union-ы */
static char _gnu_[] = "Compiled with GNU C-compiler";
Info mwrk_info[] = {    /* строки для главного меню      */
    { "\\Current directory",            0       },
    { "\\Root directory",               M_HATCH },
    { "\\Menus",                        0       },
    { "\1", /* гориз. черта */          0       },
    { "\\Help",                         0       },
    { "Un\\implemented",                I_NOSEL },
    { "Change \\sorttype",              0       },
    { "Look directory \\history",       0       },
    { "\1", /* гориз. черта */          0       },
    { "\\Quit",                         M_BOLD  },
    { "\1", /* гориз. черта */          0       },
    { "\\Redraw screen",                M_HATCH },
    { "Chdir both panels to /",         M_HATCH },
    { NULL, 0 }
};
void mwrk_init(){
    mwrk_info [0].any.act = f_left;
    mwrk_info [1].any.act = f_right;
    mwrk_info [2].any.act = f_pull;
    mwrk_info [4].any.act = f_help;
    mwrk_info [6].any.act = f_sort;
    mwrk_info [7].any.act = f_dir;
    mwrk_info [9].any.act = f_bye;
    mwrk_info[11].any.act = f_redraw;
    mwrk_info[12].any.act = f_cdroot;
}
#endif
char *mwrk_help[] = {
      "Перейти в левую панель",  "Перейти в правую панель",
      "Перейти в строчное меню", "",
      "Выдать подсказку",        "Не реализовано",
      "Изменить тип сортировки имен", "История путешествий",
      "", "Выход", "", "Перерисовка экрана",
      "Обе панели поставить в корневой каталог", NULL
};
void m_help(Menu *m, int n, int among){
     Message(mwrk_help[n]);    }
/* Выбор в рабочем (командном) меню */
void SelectWorkingMenu(int sel){
    if(sel == NOSELECTED)
       sel = MnuUsualSelect( & mwrk, NO);
    if( M_REFUSED(&mwrk)) help("Выбери Quit", NORUN);
    else if(mwrk.items[sel].any.act)
	  (*mwrk.items[sel].any.act)();
    if( !done) MnuHide( & mwrk );
}
f_left ()  { current_menu = SEL_PANE1; return 0; }
f_right()  { current_menu = SEL_PANE2; return 0; }
f_pull ()  { current_menu = SEL_PULL;  return 0; }
f_help ()  { help("Нажми ENTER или набери команду:", RUNCMD);
	     return 0; }
f_sort ()  { SelectSortType(NOSELECTED); return 0; }
f_dir  ()  { Info *idir; if(idir = HistSelect(&hcwd, 20, 3))
	       cd(idir->s, &tpane2, CWD);
	    current_menu = SEL_PANE2;    return 0; }
f_bye   () { done++;                     return 0; }
f_redraw() { RedrawScreen();             return 0; }
f_cdroot() { cd("/", &tpane1, CWD);
	     cd("/", &tpane2, CWD); checkBothPanes();
	     return 0;                             }
/*-----------------------------------------------------------------*
 *  Выдача информации про файл, редактирование кодов доступа.      *
 *-----------------------------------------------------------------*/
void MYwaddstr(WINDOW *w, int y, int x, int maxwidth, char *s){
     register pos;
     for(pos=0; *s && *s != '\n' && pos < maxwidth; ++s){
	 wmove(w, y, x+pos);
	      if( *s == '\t')  pos += 8 - (pos & 7);
	 else if( *s == '\b'){ if(pos)  --pos; }
	 else if( *s == '\r')  pos = 0;
	 else { ++pos; waddch(w, isprint(*s) ? *s : '?'); }
     }
}
/* Просмотр начала файла в противоположной панели.            */
void fastView(
     char *name,    /* имя файла                              */
     unsigned mode, /* некоторые типы файлов не просматривать */
     Table *otbl    /* противоположная панель                 */
){   FILE *fp; register int x, y; char buf[512];

     TblClear(otbl);
     Message("Нажми ENTER для окончания. "
	     "ПРОБЕЛ - изменяет код доступа. "
	     "ESC - откатка.");
     if( !ISREG(mode)) goto out;
     if((fp = fopen(name, "r")) == NULL){
	   Message("Не могу читать %s", name); return;
     }
     for(y=0; y < otbl->height && fgets(buf, sizeof buf, fp); y++)
	 MYwaddstr(panewin, otbl->top+y, otbl->left+1,
		   otbl->width-2, buf);
     fclose(fp);
out: wrefresh(otbl->win);   /* проявить */
}
static struct attrNames{
	unsigned mode; char name; char acc; int off;
} modes[] = {
	{ S_IREAD,       'r', 'u',  0    },
	{ S_IWRITE,      'w', 'u',  1    },
	{ S_IEXEC,       'x', 'u',  2    },
	{ S_IREAD  >> 3, 'r', 'g',  3    },
	{ S_IWRITE >> 3, 'w', 'g',  4    },
	{ S_IEXEC  >> 3, 'x', 'g',  5    },
	{ S_IREAD  >> 6, 'r', 'o',  6    },
	{ S_IWRITE >> 6, 'w', 'o',  7    },
	{ S_IEXEC  >> 6, 'x', 'o',  8    },
};
#define NMODES (sizeof(modes)/sizeof(modes[0]))

/* Позиция в которой изображать i-ый бит кодов доступа */
#define MODE_X_POS(tbl, i) (tbl->left + DIR_SIZE + 12 + modes[i].off)
#define MODE_Y_POS(tbl)    (tbl->top  + tbl->height + 1)

#ifdef FILF
/* Изобразить информацию о текущем выбранном файле */
void showMode(Table *tbl, int attr){
   Info *inf   = & tbl->items[tbl->current];    /* файл   */
   register i; unsigned mode = inf->mode;       /* коды   */
   int     uid = inf->uid, gid = inf->gid;      /* хозяин */
   /* идентификаторы хозяина и группы процесса-коммандера */
   static char first = YES; static int myuid, mygid;
   WINDOW *win = tbl->win;
   int xleft   = tbl->left + 1, y = MODE_Y_POS(tbl);

   if( first ){ first = NO; myuid = getuid(); mygid = getgid(); }
   wattron  (win, attr);
   mvwprintw(win, y, xleft, " %*.*s %8ld ",  /* имя файла */
      -DIR_SIZE, DIR_SIZE,
       inf->s ? (!strcmp(inf->s, "..") ? "<UP-DIR>": inf->s) :
		"(EMPTY)",
       inf->size);
   /* тип файла (обычный|каталог|устройство) */
   wattron (win, A_ITALICS|A_BOLD);
   waddch  (win, ISDIR(mode) ? 'd': ISDEV(mode) ? '@' : '-');
   wattroff(win, A_ITALICS|A_BOLD);
   /* коды доступа */
   for(i=0; i < NMODES; i++){
       if((modes[i].acc == 'u' && myuid == uid) ||
	  (modes[i].acc == 'g' && mygid == gid) ||
	  (modes[i].acc == 'o' && myuid != uid && mygid != gid)) ;
       else     wattron(win, A_ITALICS);
       mvwaddch(win, y, MODE_X_POS(tbl, i),
		mode & modes[i].mode ? modes[i].name : '-');
       wattroff(win, A_ITALICS);
   }
   waddch(win, ' '); wattroff(win, attr);
}
#define newmode (tbl->items[tbl->current].mode)
/* Редактирование кодов доступа к файлам. */
int editAccessModes(FileWidget *wd){
    Table *tbl  = &wd->t;
    Table *otbl = &(Other_pane(wd)->t); /* или Other_tbl(tbl); */
    unsigned prevmode, oldmode;         /* старый код доступа  */
    char *name;                         /* имя текущего файла  */
    WINDOW *win = tbl->win;
    int position = 0, c;

    for(;;){  /* Цикл выбора файлов в таблице */
	name = T_ITEMF(tbl, tbl->current, 0);
	oldmode = newmode;             /* запомнить */
	fastView(name, newmode, otbl); /* показать первые строки файла */

	for(;;){  /* Цикл обработки выбранного файла */
	   wmove(win, MODE_Y_POS(tbl), MODE_X_POS(tbl, position));

	   switch(c = WinGetch(win)){
/* Некоторые клавиши вызывают перемещение по таблице */
case KEY_BACKTAB: TblPointAt(tbl, tbl->current - tbl->height); goto mv;
case '\t':        TblPointAt(tbl, tbl->current + tbl->height); goto mv;
case KEY_UP:      TblPointAt(tbl, tbl->current - 1); goto mv;
case KEY_DOWN:    TblPointAt(tbl, tbl->current + 1); goto mv;
case KEY_HOME:    TblPointAt(tbl, 0);                goto mv;
case KEY_END:     TblPointAt(tbl, tbl->nitems-1);    goto mv;
/* Прочие клавиши предназначены для редактирования кодов доступа */
	   case KEY_LEFT:  if(position) --position; break;
	   case KEY_RIGHT: if(position < NMODES-1)  position++; break;
	   default: goto out;
	   case ESC:    /* Восстановить старые коды */
		prevmode = newmode = oldmode; goto change;
	   case ' ':    /* Инвертировать код доступа */
		prevmode = newmode;              /* запомнить */
		newmode ^= modes[position].mode; /* инвертировать */
change:         if( chmod(name, newmode) < 0){
		    beep();
		    Message("Не могу изменить доступ к %s", name);
		    newmode = prevmode; /* восстановить */
		} else /* доступ изменен, показать это */
		    showMode(tbl, A_REVERSE);
		break;
	   }
	} /* Конец цикла обработки выбранного файла */
mv:     ;
    } /* Конец цикла выбора файлов в таблице */
out:
    /* Очистить противоположную панель после fastView(); */
    Message(""); TblClear(otbl); return c;
}
#undef newmode
#else
void editAccessModes(FileWidget *wd){}
#endif
long diskFree(){
      struct ustat ust; struct stat st; long freespace;
      if(stat(".", &st) < 0) return 0;
      ustat(st.st_dev, &ust);
      freespace = ust.f_tfree * 512L; freespace /= 1024;
      Message("В %*.*s свободно %ld Кб.",
	 -sizeof(ust.f_fname), sizeof(ust.f_fname),
	 *ust.f_fname ? ust.f_fname : ".", freespace);
      doupdate();  /* проявить окно для Message() */
      return freespace;
}
/*-----------------------------------------------------------------*
 *   Специальные команды, использующие обход дерева
 *-----------------------------------------------------------------*/
/* Выдача сообщений об ошибках (смотри Makefile) */
int tree_err_cant_read(char *name){
    Message("Не могу читать \"%s\"", name); return WARNING;
}
int tree_name_too_long(){
    Message("Слишком длинное полное имя"); return WARNING;
}
char canRun;  /* продолжать ли поиск */
/* Прерывание обхода по SIGINT */
void onintr_f(nsig){ canRun = NO; Message("Interrupted"); }

/* ==== место, занимаемое поддеревом ==== */
long tu(int *count){
   struct stat st; register i; long sum = 0L;
   *count = 0;
   for(i=0; i < A_tbl->nitems ;++i )
      if( T_TST(A_tbl, i, T_LABEL)){
	  stat(T_ITEMF(A_tbl, i, 0), &st);
#define KB(s)   (((s) + 1024L - 1) / 1024L)
	  sum += KB(st.st_size); (*count)++;
      }
   return sum;
}
void diskUsage(){ long du(), size, sizetagged; int n;
  char msg[512];
  Message("Измеряем объем файлов..."); doupdate();
  size = du(".");   diskFree();  sizetagged = tu(&n);
  sprintf(msg, "%ld килобайт в %s, %ld кб в %d помеченных файлах",
		size,          CWD, sizetagged,   n);
  help(msg, NORUN);
}
/* ==== поиск файла ===================== */
extern char *find_PATTERN;             /* imported from treemk.c */
extern Info gargv[]; extern int gargc; /* imported from glob.c   */
/* Проверить очередное имя и запомнить его, если подходит */
static int findCheck(char *fullname, int level, struct stat *st){
    char *basename = strrchr(fullname, '/');
    if(basename) basename++;
    else         basename = fullname;
    if( canRun == NO ) return FAILURE;   /* поиск прерван         */
    if( match(basename, find_PATTERN)){  /* imported from match.c */
	gargv[gargc]     = NullInfo;  /* зачистка */
	gargv[gargc].s   = strdup(fullname);
	gargv[gargc++].fl= ISDIR(st->st_mode) ? I_DIR : 0;
	gargv[gargc]     = NullInfo;
	Message("%s", fullname); doupdate();
    }
    /* Страховка от переполнения gargv[] */
    if   ( gargc < MAX_ARGV - 1 ) return SUCCESS;
    else { Message("Найдено слишком много имен."); return FAILURE; }
}
/* Собрать имена файлов, удовлетворяющие шаблону */
static Info *findAndCollect(char *pattern){
     void (*old)() = signal(SIGINT, onintr_f);
     Sort saveSort;

     find_PATTERN = pattern; canRun = YES;
     Message("Ищем %s от %s", pattern, CWD); doupdate();
     greset();  /* смотри glob.c, gargc=0; */
     walktree(CWD, findCheck, NULL, findCheck);
     signal(SIGINT, old);
     saveSort = sorttype; sorttype = SORT_ASC;
     if(gargc) qsort( gargv, gargc, sizeof(Info), gcmps);
     sorttype = saveSort;
     return gargc ? blkcpy(gargv) : NULL;
}
/* Обработать собранные имена при помощи предъявления меню с ними */
void findFile(FileWidget *wd){
     static Info *found; static Menu mfind;
     int c; Table *tbl = & wd->t;
     char *pattern = help("Введи образец для поиска, вроде *.c, "
			  "или ENTER для прежнего списка", FIND);
     if( LE_REFUSED( &edit)) return; /* отказались от поиска */
     /* Если набрана пустая строка, help() выдает NULL       */
     if( pattern ){            /* задан новый образец - ищем */
	 /* Уничтожить старый список файлов и меню */
	 if( found ) blkfree( found );
	 MnuDeinit( &mfind );
	 found = findAndCollect(pattern); /* поиск */
	 HistAdd( &hpat, pattern, 0);
	 /* Образуем меню из найденных файлов */
	 if( found ){  /* если что-нибудь нашли */
	   mfind.items     =  found;
	   mfind.title     =  pattern ? pattern : "Найденные файлы";
	   mfind.top       =  3; mfind.left = COLS/6;
	   mfind.bg_attrib =  A_STANDOUT; mfind.sel_attrib = A_REVERSE;
	   MnuInit (&mfind);
	 }
     } /* else набрана пустая строка - просто вызываем список
	* найденных ранее файлов.
	*/
     if( found == NULL ){
	 Message("Ничего не найдено"); beep(); return;
     }
     c = MnuUsualSelect(&mfind, NO);
     /* Выбор файла в этом меню вызовет переход в каталог,
      * в котором содержится этот файл */
     if( !M_REFUSED( &mfind )){
	char *s = M_ITEM(&mfind, mfind.current);

	/* пометить выбранный элемент */
	M_SET(&mfind, mfind.current, M_LABEL);
	/* если это каталог - войти в него */
	if( M_TST(&mfind, mfind.current, I_DIR))
	       cd(s, wd, CWD);
	/* иначе войти в каталог, содержащий этот файл */
	else { char *p; struct savech svch; /* смотри glob.h */
	     SAVE( svch, strrchr(s, '/'));   *svch.s = '\0';
	     p = strdup(s); RESTORE(svch);
	     if( !strcmp(CWD, p))               /* мы уже здесь     */
		 TblPlaceByName(tbl, svch.s+1); /* указать курсором */
	     else /* изменить каталог и указать курсором на файл s  */
		 cd(p, wd, s);
	     free(p);
	}
     }
     MnuHide(&mfind);  /* спрятать меню, не уничтожая его */
}
/*-----------------------------------------------------------------*
 *   Работа с панелями, содержащими имена файлов двух каталогов.   *
 *-----------------------------------------------------------------*/
/* Восстановить элементы, затертые рамкой WinBorder */
void t_restore_corners(){
    mvwaddch(panewin, LINES-3, 0,               LEFT_JOIN);
    mvwaddch(panewin, LINES-3, COLS-2-BARWIDTH, RIGHT_JOIN);
    mvwaddch(panewin, LINES-5, 0,               LEFT_JOIN);
    mvwaddch(panewin, LINES-5, COLS-2-BARWIDTH, RIGHT_JOIN);
    mvwaddch(panewin, 2,       CENTER, TOP_JOIN);
    wattron (panewin, A_BOLD);
    mvwaddch(panewin, LINES-3, CENTER, BOTTOM_JOIN);
    mvwaddch(panewin, LINES-5, CENTER, MIDDLE_CROSS);
    wattroff(panewin, A_BOLD);
}
/* Нарисовать нечто при входе в панель. Здесь изменяется
 * заголовок окна: он становится равным имени каталога,
 * просматриваемого в панели */
void t_enter(Table *tbl){
     WinBorder(tbl->win, tbl->bg_attrib, tbl->sel_attrib,
	       CWD, BAR_VER|BAR_HOR, NO);
     t_restore_corners();
}
/* Стереть подсветку при выходе из панели */
void t_leave(Table *tbl){ TblDrawItem( tbl, tbl->current, NO, YES ); }
/* Рисует недостающую часть рамки, которая не изменяется впоследствии */
void t_border_common(){
    WinBorder(panewin, A_tbl->bg_attrib, A_tbl->sel_attrib,
	      A_dir->name, BAR_VER|BAR_HOR, NO);
    wattron (panewin, A_BOLD);
    whorline(panewin, LINES-3, 1, COLS-1-BARWIDTH-1);
    whorline(panewin, LINES-5, 1, COLS-1-BARWIDTH-1);
    wverline(panewin, CENTER, A_tbl->top, A_tbl->top + A_tbl->height+2);
    wattroff(panewin, A_BOLD);
    t_restore_corners();
}
/* Функция, изображающая недостающие части панели при входе в нее */
int t_show(Table *tbl){
#ifdef FILF
     showMode(A_tbl, A_STANDOUT); showMode(B_tbl, A_STANDOUT);
#endif
     return 1;
}
void t_scrollbar(Table *tbl, int whichbar, int n, int among){
     WinScrollBar(tbl->win, BAR_VER|BAR_HOR, n, among,
		  "Yes", tbl->bg_attrib);
#ifdef FILF
     showMode(tbl, A_REVERSE);
#endif
}
/* Особая обработка клавиш при выборе в таблице */
int t_hit[] = {
   '\t',        KEY_F(1),       KEY_F(2),       KEY_F(3),
   KEY_F(4),    KEY_F(8),       ' ',            '+',
   '-',         ctrl('R'),      ctrl('L'),      ctrl('F'),
   -1 };
Info t_info[] = {
  { "TAB    Перейти в другую панель",        0},
  { "F1     Выдать подсказку",               0},
  { "F2     Ввести команду",                 0},
  { "F3     Перейти в родительский каталог", 0},
  { "F4     Перейти в каталог по имени",     0},
  { "F8     Удалить помеченные файлы",       0},
  { "ПРОБЕЛ Редактировать коды доступа",     0},
  { "+      Пометить файлы",                 0},
  { "-      Снять пометки",                  0},
  { "ctrl/R Перечитать каталог",             0},
  { "ctrl/L Выдать размер файлов в каталоге",0},
  { "ctrl/F Поиск файла",                    0},
  { NULL, 0}
};
int t_help(){
   static Menu mth; int c = 0;
   if( mth.items == NULL ){
       mth.items     =  t_info;
       mth.title     =  "Команды в панели";
       mth.top       =  3; mth.left = COLS/6;
       mth.bg_attrib =  A_STANDOUT; mth.sel_attrib = A_REVERSE;
       MnuInit (&mth);
       mth.hotkeys   = t_hit;
   }
   c = MnuUsualSelect(&mth, 0);
   /* Спрятать меню, не уничтожая его. Уничтожение выглядело бы так:
    *   mth.hotkeys = NULL; (т.к. они не выделялись malloc()-ом)
    *   MnuDeinit(&mth);
    */
   MnuHide(&mth);
   if( M_REFUSED(&mth)) return 0;             /* ничего не делать */
   return t_hit[c];  /* клавиша, соответствующая выбранной строке */
}
int t_handler (Table *tbl, int c, HandlerReply *reply){
    int i, cnt=0; extern int unlink(), rmdir(); char *answer;
    FileWidget  *wd = TblFW (tbl);
    switch(c){
    case '\t':  /* перейти в соседнюю панель */
	ExchangePanes();
	*reply = HANDLER_OUT; return LEAVE_KEY; /* покинуть эту панель */
    case KEY_F(1): *reply = HANDLER_NEWCHAR; return t_help();
    case KEY_F(2):
	   (void) Edit(tbl->win, T_ITEMF(tbl, tbl->current, 0), RUNCMD);
	   break;
    case KEY_F(3): cd(".." , wd, CWD); break;
    case KEY_F(4):
      if(answer = help("Введи имя каталога, в который надо перейти",CHDIR))
	 cd(answer , wd, CWD);
      break;
    case ctrl('R'): break;
    case KEY_F(8):
      for(i=0; i < tbl->nitems; i++)
	 if(T_TST(tbl, i, M_LABEL)){  int code; cnt++;
if((code = (T_TST(tbl, i, I_DIR) ? rmdir : unlink) (T_ITEMF(tbl, i,0))) < 0)
	    T_SET(tbl, i, M_HATCH);
      }
      if(cnt==0) help("Нет помеченных файлов", NORUN);
      break;
    case '+':
      if(answer = help("Шаблон для пометки", TAG))
	 TblTagAll(tbl, answer, T_LABEL);
      break;
    case '-':
      if(answer = help("Шаблон для снятия пометок", TAG))
	 TblUntagAll(tbl, answer, T_LABEL);
      break;
    case ctrl('L'):     /* команда "disk usage" */
      diskUsage(); break;
    case ctrl('F'):     /* поиск файла */
      findFile(wd); break;
    case ' ':           /* редактирование кодов доступа */
      editAccessModes(wd); break;
    }
    *reply = HANDLER_OUT; return REPEAT_KEY;
    /* вернуться в эту же панель */
}
/* Выбор в одной из панелей. */
int SelectPane(FileWidget *wd){
    Table *tbl       = & wd->t;
    DirContents *d   = & wd->d;
    int sel, retcode = 0;

    RaiseWin( tbl->win );
 /* войти в указанный каталог, поправить CWD  */
    if(mychdir( d->name ) < 0) checkBothPanes();
    /* t_enter( tbl );  /* войти в указанную панель, поправить рамку */
    for(;;){
      /* Проверить, не устарело ли содержимое таблиц */
      checkBothPanes();
      if((sel = TblUsualSelect( tbl )) == TOTAL_NOSEL ){
	  current_menu = SEL_PULL; goto out;           }
      if( T_REFUSED(tbl)) break; /* нажат ESC */
      if( tbl->key == LEAVE_KEY  ){ retcode=1; break; }
      strcpy(SELECTION, T_ITEMF(tbl, sel, 0));
      if( tbl->key == REPEAT_KEY ) continue;
      if(T_TST(tbl, sel, I_DIR)){ /* это каталог */
      /* попытаться перейти в этот каталог */
	 cd(SELECTION, wd, CWD);
      } else if(T_TST(tbl, sel, I_EXE)){ /* выполняемый файл */
	 (void) Edit(tbl->win, SELECTION, RUNCMD);
      } else {
	 editAccessModes(wd);
      /* На самом деле надо производить подбор команды по
       * типу файла (набор соответствий должен программироваться
       * вами в специальном файле, считываемом при запуске коммандера).
       *        runCommand( classify(SELECTION));
       * где классификация в простейшем случае - по имени и суффиксу,
       * а в более развитом - еще и по кодам доступа (включая тип файла)
       * и по первой строке файла (или "магическому числу").
       */
       }
    }  /* end for */
    t_leave( tbl );
out:
    if( !retcode ) current_menu = SEL_PULL; /* выход по ESC */
    return retcode;
}
/*-----------------------------------------------------------------*
 *   Горизонтальное командное меню (вызывается по ESC).            *
 *-----------------------------------------------------------------*/
PullInfo pm_items [] = {                 /* подсказка */
  {{ " \\Left ",     0 },        NULL,   "Left pane"       }, /* 0 */
  {{ " \\Commands ", 0 },        &mwrk,  "Do some commands"}, /* 1 */
  {{ " \\Tools ",    PM_NOSEL }, NULL,   ""                }, /* 2 */
  {{ " \\Sorttype ", 0 },        &msort, "Change sort type"}, /* 3 */
  {{ " \\Right ",    0 },        NULL,   "Right pane"      }, /* 4 */
  {{ NULL,           0 },        NULL,   NULL }
};
void p_help(PullMenu *p, int n, int among){ Message( PM_NOTE(p, n)); }
/* Выбор в меню-строке */
void SelectPullMenu(){
    int c, sel; Menu *m;
    for(;current_menu == SEL_PULL;){
	c = PullUsualSelect(&pull);
	sel = pull.current;
	if( PM_REFUSED(&pull)){ current_menu = previous_menu; return;}
	switch(sel){
	case 0: current_menu = SEL_PANE1; return;
	case 1: SelectWorkingMenu(c);     return;
	case 2:                           return;  /* не бывает */
	case 3: SelectSortType(c);        return;
	case 4: current_menu = SEL_PANE2; return;
	}
    }
}
/*-----------------------------------------------------------------*
 *   Инициализация и завершение.                                   *
 *-----------------------------------------------------------------*/
void die(int sig){
     echo(); nocbreak(); mvcur(-1,-1,LINES-1,0);
     refresh(); endwin (); putchar('\n');
     if(sig) printf("Signal %d\n", sig);
     if(sig == SIGSEGV) abort(); else exit(sig);
}
void main (void) {
    setlocale(LC_ALL, "");  /* получить информацию о языке диагностик */
    initscr ();          /* включить curses */
    signal(SIGINT, die); /* по сигналу вызывать die(); */
    signal(SIGBUS, die); /* по нарушению защиты памяти */
    signal(SIGSEGV,die);
    refresh();           /* обновить экран: это очистит его */
    noecho(); cbreak();  /* выключить эхо, включить прозрачный ввод */
/* Проинициализировать истории */
    HistInit(&hcwd,  20); hcwd. mnu.title = "История пути";
    HistInit(&hedit, 20); hedit.mnu.title = "История команд";
    HistInit(&hpat,   8); hpat. mnu.title = "Шаблоны имен";
/* Разметить меню сортировки   */
    msort.items     = sort_info;
    msort.title     = "Вид сортировки каталога";
    msort.top       = 1; msort.left = 2;
    msort.showMe    = sort_show;
    msort.bg_attrib = A_NORMAL; msort.sel_attrib = A_STANDOUT;
    /* MnuInit (&msort); инициализируется в pull-menu */
/* Разметить рабочее меню */
    mwrk.items      =  mwrk_info;
    mwrk.title      = "Главное меню";
    mwrk.top        = 1;    mwrk.left = COLS/3;
    mwrk.handler    = NULL; mwrk.hitkeys = NULL;
    mwrk.bg_attrib  = A_STANDOUT; mwrk.sel_attrib = A_REVERSE;
    mwrk.scrollBar  = m_help;
#ifdef __GNUC__
    mwrk_init();
#endif
    /* MnuInit (&mwrk); инициализируется в pull-menu */
/* Разметить левую и правую панели */
    tpane1.t.width      = CENTER - 1;
    tpane2.t.width      = COLS - tpane1.t.width - 2 - (2 + BARWIDTH);
    tpane1.t.height     = tpane2.t.height = (LINES - 8);
    tpane1.t.win        = tpane2.t.win  = panewin = stdscr;
    tpane1.t.left       = 1;
    tpane2.t.left       = CENTER+1;
    tpane1.t.top        = tpane2.t.top    = 3;
    tpane1.t.bg_attrib  = tpane2.t.bg_attrib  = A_NORMAL;
    tpane1.t.sel_attrib = tpane2.t.sel_attrib = A_STANDOUT;
    tpane1.t.scrollBar  = tpane2.t.scrollBar  = t_scrollbar;
    tpane1.t.hitkeys    = tpane2.t.hitkeys    = t_hit;
    tpane1.t.handler    = tpane2.t.handler    = t_handler;
    tpane1.t.showMe     = tpane2.t.showMe     = t_show;
    tpane1.t.hideMe     = tpane2.t.hideMe     = NULL;
/* Разметить имена для файловых объектов */
    tpane1.d.name = strdup("Текущий каталог");
    tpane2.d.name = strdup("Корневой каталог");
/* Изобразить рамки (но пока не проявлять их)
 * Это надо сделать до первого cd(), т.к. иначе при неудаче будет выдано
 * сообщение, которое проявит НЕЗАВЕРШЕННУЮ картинку */
    t_border_common(); t_restore_corners();
/* Доразметить левую панель */
    mychdir(".");  /* узнать полное имя текущего каталога в CWD[] */
    /* прочитать содержимое каталога CWD в tpane1.d */
    cd( CWD , &tpane1, CWD);
    tpane1.t.fmt        = "directory";
    InitTblFromDir(&tpane1, NO, NULL);
/* Доразметить правую панель */
    tpane2.t.fmt = NULL;
    /* прочитать содержимое каталога "/" в tpane2.d */
    cd( "/", &tpane2, CWD); /* теперь стоим в корне */
/* Вернуться в рабочий каталог */
    cd( tpane1.d.name, &tpane1, CWD);
/* Нарисовать обе панели */
    TblDraw(A_tbl); TblDraw(B_tbl);
/* Разметить pulldown меню */
    pull.bg_attrib  = A_REVERSE; pull.sel_attrib = A_NORMAL;
    pull.items      = pm_items;  pull.scrollBar  = p_help;
    PullInit(&pull);
/* Основной цикл */
    for(done=NO, current_menu=SEL_PANE1, A_pane= &tpane1, B_pane= &tpane2;
	done == NO; ){
	Message("");
	if(SEL_PANE) previous_menu = current_menu;
	switch(current_menu){
	case SEL_WRK :  SelectWorkingMenu(NOSELECTED); break;
	case SEL_PULL:  SelectPullMenu();              break;
	case SEL_PANE1: if( SelectPane(&tpane1) < 0)
			    M_SET(&mwrk, 0, I_NOSEL);  break;
	case SEL_PANE2: if( SelectPane(&tpane2) < 0)
			    M_SET(&mwrk, 0, I_NOSEL);  break;
	}
    }
    die(0);     /* Завершить работу */
}

